import serial
import time
import os
import threading
import hashlib
from urllib.parse import urlparse, parse_qs
from PyQt5.QtSerialPort import QSerialPort
from PyQt5.QtCore import QByteArray

class SerialReader(threading.Thread):
    def __init__(self, ser, output_file, com_port='COM3', baud_rate=115200, 
                 task_id=181537, device_id=170856841234567, access_token='JnBHGnQsFG',
                 total_size=202317, read_len=1024, version='1.0.0'):
        """
        初始化串口读取器
        
        参数:
            ser: 串口对象，可以是PySerial的Serial对象或QSerialPort对象
            output_file: 输出文件路径
            com_port: 串口号，默认为'COM3'
            baud_rate: 波特率，默认为115200
            task_id: 任务ID，默认为181537
            device_id: 设备ID，默认为170856841234567
            access_token: 访问令牌，默认为'JnBHGnQsFG'
            total_size: 数据总大小，默认为202317
            read_len: 每次读取的长度，默认为1024
            version: 固件版本，默认为'1.0.0'
        """
        super(SerialReader, self).__init__()
        self.ser = ser
        self.com_port = com_port
        self.baud_rate = baud_rate
        self.output_file = output_file
        self.stop_event = threading.Event()
        self.lock = threading.Lock()
        self.condition = threading.Condition(self.lock)
        self.response_ready = False  # 响应读取标志
        # QHTTPGETEX指令参数
        self.total_size = total_size  # 数据总大小
        self.current_position = 0  # 当前读取位置
        self.read_len = read_len  # 每次读取的长度
        # 升级相关参数
        self.task_id = task_id  # 任务ID
        self.device_id = device_id  # 设备ID
        self.access_token = access_token  # 访问令牌
        self.url = f'https://iot.ctwing.cn/dmfile/file/mqtt/upgrade?fileId=2fb1f7fc142145cd996e2014313d357e&taskId={self.task_id}&deviceId={self.device_id}&accessToken={self.access_token}'
        self.version = version  # 固件版本
        # 校验相关参数
        self.file_id = None  # 新增变量保存 fileId
        self.received_data_md5 = None  # 保存接收到数据的 MD5 签名
        self.download_complete = False  # 下载完成标志
        self.download_progress = 0  # 下载进度
        self.is_qserial = isinstance(ser, QSerialPort)  # 标记是否是QSerialPort

    def _write(self, data):
        """内部方法：写入数据到串口"""
        if self.is_qserial:
            if isinstance(data, str):
                data = data.encode('utf-8')
            if isinstance(data, bytes):
                qdata = QByteArray(data)
                return self.ser.write(qdata)
            return self.ser.write(data)
        else:
            return self.ser.write(data)

    def _read(self, size=1):
        """内部方法：从串口读取数据"""
        if self.is_qserial:
            if self.ser.waitForReadyRead(100):
                return self.ser.read(size).data()
            return b''
        else:
            return self.ser.read(size)

    def run(self):
        """
        线程运行函数，读取串口数据并保存到文件
        """
        with open(self.output_file, 'ab') as f:
            count = 0       
            while not self.stop_event.is_set():
                with self.condition:  # 使用条件变量锁
                    if self.is_qserial:
                        # 使用QSerialPort读取数据
                        if self.ser.waitForReadyRead(100):
                            data = self.ser.readAll().data()
                        else:
                            data = b''
                    else:
                        # 使用PySerial读取数据
                        data = self.ser.read(1200)  # 每次读取1200字节
                    
                    if data:
                        if self.is_binary_data(data):
                            # 截去开头的字符串数据（以 CONNECT\r\n 截止）
                            if b'CONNECT\r\n' in data:
                                data = data.split(b'CONNECT\r\n', 1)[1]
                            # 截去结尾的字符串数据（以 \r\nOK\r\n 开始）
                            if b'\r\nOK\r\n' in data:
                                data = data.split(b'\r\nOK\r\n', 1)[0]
                            f.write(data)
                            count += 1
                            print(f"接收到二进制数据:{data}")
                            print(f"接收到第{count}个数据块，当前位置：{self.current_position}")
                            # 更新下载进度
                            if self.total_size > 0:
                                self.download_progress = int((self.current_position / self.total_size) * 100)
                        else:
                            # 将 \r\n 转换成实际的回车换行符
                            response_str = data.decode().replace('\r\n', '\n')
                            print(f"接收到响应数据:\n{response_str}")
                    self.response_ready = True
                    self.condition.notify()  # 通知写操作可以继续
                    
                time.sleep(0.1)  # 等待0.1秒，避免过度占用CPU资源
            
            if self.current_position >= self.total_size:
                # 计算文件数据的 MD5
                f.seek(0)  # 重置指针到文件开头
                file_data = f.read()  # 读取所有数据
                self.received_data_md5 = hashlib.md5(file_data).hexdigest()  # 计算 MD5
                print(f"接收到数据的 MD5 签名: {self.received_data_md5}")
                # 比较 fileId
                if self.file_id:
                    print(f"与提取的 fileId 进行比对: {self.file_id}")
                self.download_complete = True
                self.download_progress = 100
            print(f"数据接收完成，文件保存为 {self.output_file}")

    def stop(self):
        """
        停止线程运行
        """
        self.stop_event.set()

    def send_data(self, data):
        """
        向串口发送数据并读取响应
        
        参数:
            data: 要发送的数据，字节类型
        """
        with self.condition:  # 使用条件变量
            # 检查串口是否打开
            if (self.is_qserial and self.ser.isOpen()) or (not self.is_qserial and self.ser.is_open):
                # 在数据结尾添加回车换行符
                data_with_crlf = data + b'\r\n'
                # 处理 AT+QHTTPURL 指令
                if data.startswith(b'AT+QHTTPURL'):
                    url_input = input("请输入 URL: ")
                    parsed_url = urlparse(url_input)
                    query_params = parse_qs(parsed_url.query)
                    if 'fileId' in query_params:
                        self.file_id = query_params['fileId'][0]  # 提取 fileId
                        print(f"提取到的 fileId: {self.file_id}")
                # 如果是 AT+QHTTPGETEX 指令，自动分段读取数据
                if data.startswith(b'AT+QHTTPGETEX'):
                    # 需要发送的AT
                    # ATE0
                    # AT+QHTTPURL=149,80
                    # https://iot.ctwing.cn/dmfile/file/mqtt/upgrade?fileId=8232cf24f6604e008bcf804e42912d62&taskId=182679&deviceId=170856841234567&accessToken=cBuztENkys
                    # AT+QHTTPGETEX
                    while self.current_position < self.total_size:
                        # 计算下一段数据的起始位置和长度
                        next_position = self.current_position + self.read_len
                        if next_position > self.total_size:
                            next_position = self.total_size
                        read_len = next_position - self.current_position

                        # 发送分段GET指令
                        self.condition.wait_for(lambda: self.response_ready)
                        self.response_ready = False
                        next_command = f'AT+QHTTPGETEX=80,{self.current_position},{read_len}\r\n'.encode()
                        self._write(next_command)
                        print(f"已发送GETEX指令: {next_command}")   

                        time.sleep(2)                                       

                        self.condition.wait_for(lambda: self.response_ready)
                        self.response_ready = False
                        # 发送 AT+QHTTPREAD=80 指令
                        read_command = b'AT+QHTTPREAD=80\r\n'
                        self._write(read_command)
                        print(f"已发送读指令: {read_command}")
                        
                        # 更新当前读取位置
                        self.current_position = next_position
                    # 分段GET结束后，上报升级进度
                    progress_report = f'AT+CTMQPUB="/ota/progress",0,0,"{{"taskId":{self.task_id},"params":{{"step":100,"desc":"Done","module":"MCU"}}}}"'.encode() + b'\r\n'
                    self._write(progress_report)
                    print(f"已发送升级进度报告: {progress_report}")

                    time.sleep(1)  

                    # 上报升级后固件版本
                    firmware_version_report = f'AT+CTMQPUB="/ota/inform",0,0,"{{"taskId":{self.task_id},"params":{{"version":"{self.version}","module":"MCU"}}}}"'.encode() + b'\r\n'
                    self._write(firmware_version_report)
                    print(f"已发送固件版本报告: {firmware_version_report}")
                        
                else:
                    self.condition.wait_for(lambda: self.response_ready)
                    self.response_ready = False
                    # 发送普通指令
                    self._write(data_with_crlf)
    
    def get_download_progress(self):
        """
        获取下载进度
        
        返回:
            下载进度百分比
        """
        return self.download_progress
    
    def is_download_complete(self):
        """
        检查下载是否完成
        
        返回:
            布尔值，表示下载是否完成
        """
        return self.download_complete
    
    def get_md5(self):
        """
        获取接收到数据的MD5签名
        
        返回:
            MD5签名字符串，如果未计算则返回None
        """
        return self.received_data_md5

    def is_binary_data(self, data):
        """
        判断数据是否为二进制数据
        
        参数:
            data: 要判断的数据
            
        返回:
            布尔值，表示数据是否为二进制
        """
        try:
            data.decode('utf-8')
            return False
        except UnicodeDecodeError:
            return True


class SOTAManager:
    """
    SOTA管理器类，用于管理SOTA升级过程
    """
    def __init__(self, com_port='COM3', baud_rate=115200, output_file=None, ser=None):
        """
        初始化SOTA管理器
        
        参数:
            com_port: 串口号，默认为'COM3'
            baud_rate: 波特率，默认为115200
            output_file: 输出文件路径，默认为None，会自动生成
            ser: 串口对象，如果提供则直接使用，不再重新打开串口
        """
        self.com_port = com_port
        self.baud_rate = baud_rate
        
        # 如果未指定输出文件，则使用默认路径
        if output_file is None:
            file_name = 'sota_download.bin'
            # 获取当前代码文件所在的目录
            current_file_path = os.path.dirname(os.path.abspath(__file__))
            # 拼接文件路径
            self.output_file = os.path.join(current_file_path, file_name)
        else:
            self.output_file = output_file
            
        self.ser = ser  # 可以直接传入串口对象
        self.reader = None
        self.external_serial = ser is not None  # 标记是否使用外部串口
        
    def open_serial_port(self):
        """
        打开串口
        
        返回:
            布尔值，表示是否成功打开串口
        """
        # 如果已经提供了串口对象，则直接返回成功
        if self.ser is not None:
            if isinstance(self.ser, QSerialPort):
                if self.ser.isOpen():
                    print(f"使用已提供的QSerialPort对象")
                    return True
            else:
                if self.ser.is_open:
                    print(f"使用已提供的PySerial对象")
                    return True
        
        try:
            self.ser = serial.Serial(self.com_port, self.baud_rate, timeout=1)
            print(f"成功打开串口 {self.com_port}，波特率: {self.baud_rate}")
            return True
        except serial.SerialException as e:
            print(f"无法打开串口 {self.com_port}: {e}")
            return False
    
    def start_reader(self, task_id=181537, device_id=170856841234567, 
                    access_token='JnBHGnQsFG', total_size=202317, 
                    read_len=1024, version='1.0.0'):
        """
        启动串口读取器
        
        参数:
            task_id: 任务ID
            device_id: 设备ID
            access_token: 访问令牌
            total_size: 数据总大小
            read_len: 每次读取的长度
            version: 固件版本
            
        返回:
            布尔值，表示是否成功启动读取器
        """
        if self.ser is None or not self.ser.is_open:
            if not self.open_serial_port():
                return False
                
        self.reader = SerialReader(
            self.ser, 
            self.output_file,
            com_port=self.com_port,
            baud_rate=self.baud_rate,
            task_id=task_id,
            device_id=device_id,
            access_token=access_token,
            total_size=total_size,
            read_len=read_len,
            version=version
        )
        self.reader.start()
        return True
    
    def send_command(self, command):
        """
        发送命令到串口
        
        参数:
            command: 要发送的命令字符串
            
        返回:
            布尔值，表示是否成功发送命令
        """
        if self.reader is None:
            print("错误：请先启动读取器")
            return False
            
        try:
            self.reader.send_data(command.encode())
            return True
        except Exception as e:
            print(f"发送命令失败: {e}")
            return False
    
    def get_progress(self):
        """
        获取下载进度
        
        返回:
            下载进度百分比
        """
        if self.reader:
            return self.reader.get_download_progress()
        return 0
    
    def is_download_complete(self):
        """
        检查下载是否完成
        
        返回:
            布尔值，表示下载是否完成
        """
        if self.reader:
            return self.reader.is_download_complete()
        return False
    
    def get_md5(self):
        """
        获取接收到数据的MD5签名
        
        返回:
            MD5签名字符串，如果未计算则返回None
        """
        if self.reader:
            return self.reader.get_md5()
        return None
    
    def stop(self):
        """
        停止SOTA管理器
        """
        if self.reader:
            self.reader.stop()
            self.reader.join()
            
        # 如果使用的是外部提供的串口，则不关闭
        if not self.external_serial:
            if self.ser and self.ser.is_open:
                self.ser.close()
                print("串口已关闭")


def open_serial_port(port, baudrate):
    """
    打开串口并返回串口对象
    
    参数:
        port: 串口号
        baudrate: 波特率
        
    返回:
        串口对象，如果打开失败则返回None
    """
    try:
        ser = serial.Serial(port, baudrate, timeout=1)
        print(f"成功打开串口 {port}，波特率: {baudrate}")
        return ser
    except serial.SerialException as e:
        print(f"无法打开串口 {port}: {e}")
        return None